/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"

#ifdef HAVE_ILMIMF

#include <OpenEXR/ImfHeader.h>
#include <OpenEXR/ImfInputFile.h>
#include <OpenEXR/ImfOutputFile.h>
#include <OpenEXR/ImfChannelList.h>
#include <OpenEXR/ImfStandardAttributes.h>
#include <OpenEXR/half.h>
#include "grfmt_exr.hpp"

#if defined _MSC_VER && _MSC_VER >= 1200
#pragma comment(lib, "Half.lib")
#pragma comment(lib, "Iex.lib")
#pragma comment(lib, "IlmImf.lib")
#pragma comment(lib, "IlmThread.lib")
#pragma comment(lib, "Imath.lib")

#undef UINT
#define UINT ((Imf::PixelType)0)
#undef HALF
#define HALF ((Imf::PixelType)1)
#undef FLOAT
#define FLOAT ((Imf::PixelType)2)
#undef uint
#define uint unsigned

#endif

namespace cv {

/////////////////////// ExrDecoder ///////////////////

ExrDecoder::ExrDecoder() {
    m_signature = "\x76\x2f\x31\x01";
    m_file = new InputFile(filename);
    m_red = m_green = m_blue = 0;
}


ExrDecoder::~ExrDecoder() {
    close();
}


void  ExrDecoder::close() {
    if (m_file) {
        delete m_file;
        m_file = 0;
    }
}

bool  ExrDecoder::readHeader() {
    bool result = false;

    if (!m_file) {   // probably paranoid
        return false;
    }

    m_datawindow = m_file->header().dataWindow();
    m_width = m_datawindow.max.x - m_datawindow.min.x + 1;
    m_height = m_datawindow.max.y - m_datawindow.min.y + 1;

    // the type HALF is converted to 32 bit float
    // and the other types supported by OpenEXR are 32 bit anyway
    m_bit_depth = 32;

    if (hasChromaticities(m_file->header())) {
        m_chroma = chromaticities(m_file->header());
    }

    const ChannelList& channels = m_file->header().channels();
    m_red = channels.findChannel("R");
    m_green = channels.findChannel("G");
    m_blue = channels.findChannel("B");
    if (m_red || m_green || m_blue) {
        m_iscolor = true;
        m_ischroma = false;
        result = true;
    } else {
        m_green = channels.findChannel("Y");
        if (m_green) {
            m_ischroma = true;
            m_red = channels.findChannel("RY");
            m_blue = channels.findChannel("BY");
            m_iscolor = (m_blue || m_red);
            result = true;
        } else {
            result = false;
        }
    }

    if (result) {
        int uintcnt = 0;
        int chcnt = 0;
        if (m_red) {
            chcnt++;
            uintcnt += (m_red->type == UINT);
        }
        if (m_green) {
            chcnt++;
            uintcnt += (m_green->type == UINT);
        }
        if (m_blue) {
            chcnt++;
            uintcnt += (m_blue->type == UINT);
        }
        m_type = (chcnt == uintcnt) ? UINT : FLOAT;

        m_isfloat = (m_type == FLOAT);
    }

    if (!result) {
        Close();
    }

    return result;
}


bool  ExrDecoder::readData(Mat& img) {
    bool color = img.channels() > 1;
    uchar* data = img.data;
    int step = img.step;
    bool justcopy = m_native_depth;
    bool chromatorgb = false;
    bool rgbtogray = false;
    bool result = true;
    FrameBuffer frame;
    int xsample[3] = {1, 1, 1};
    char* buffer;
    int xstep;
    int ystep;

    xstep = m_native_depth ? 4 : 1;

    if (!m_native_depth || (!color && m_iscolor)) {
        buffer = (char*)new float[ m_width * 3 ];
        ystep = 0;
    } else {
        buffer = (char*)data;
        ystep = step;
    }

    if (m_ischroma) {
        if (color) {
            if (m_iscolor) {
                if (m_blue) {
                    frame.insert("BY", Slice(m_type,
                                             buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep,
                                             12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0));
                    xsample[0] = m_blue->ySampling;
                }
                if (m_green) {
                    frame.insert("Y", Slice(m_type,
                                            buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4,
                                            12, ystep, m_green->xSampling, m_green->ySampling, 0.0));
                    xsample[1] = m_green->ySampling;
                }
                if (m_red) {
                    frame.insert("RY", Slice(m_type,
                                             buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8,
                                             12, ystep, m_red->xSampling, m_red->ySampling, 0.0));
                    xsample[2] = m_red->ySampling;
                }
                chromatorgb = true;
            } else {
                frame.insert("Y", Slice(m_type,
                                        buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep,
                                        12, ystep, m_green->xSampling, m_green->ySampling, 0.0));
                frame.insert("Y", Slice(m_type,
                                        buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4,
                                        12, ystep, m_green->xSampling, m_green->ySampling, 0.0));
                frame.insert("Y", Slice(m_type,
                                        buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8,
                                        12, ystep, m_green->xSampling, m_green->ySampling, 0.0));
                xsample[0] = m_green->ySampling;
                xsample[1] = m_green->ySampling;
                xsample[2] = m_green->ySampling;
            }
        } else {
            frame.insert("Y", Slice(m_type,
                                    buffer - m_datawindow.min.x * 4 - m_datawindow.min.y * ystep,
                                    4, ystep, m_green->xSampling, m_green->ySampling, 0.0));
            xsample[0] = m_green->ySampling;
        }
    } else {
        if (m_blue) {
            frame.insert("B", Slice(m_type,
                                    buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep,
                                    12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0));
            xsample[0] = m_blue->ySampling;
        }
        if (m_green) {
            frame.insert("G", Slice(m_type,
                                    buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4,
                                    12, ystep, m_green->xSampling, m_green->ySampling, 0.0));
            xsample[1] = m_green->ySampling;
        }
        if (m_red) {
            frame.insert("R", Slice(m_type,
                                    buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8,
                                    12, ystep, m_red->xSampling, m_red->ySampling, 0.0));
            xsample[2] = m_red->ySampling;
        }
        if (color == 0) {
            rgbtogray = true;
            justcopy = false;
        }
    }

    m_file->setFrameBuffer(frame);
    if (justcopy) {
        m_file->readPixels(m_datawindow.min.y, m_datawindow.max.y);

        if (color) {
            if (m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1)) {
                UpSample(data, 3, step / xstep, xsample[0], m_blue->ySampling);
            }
            if (m_green && (m_green->xSampling != 1 || m_green->ySampling != 1)) {
                UpSample(data + xstep, 3, step / xstep, xsample[1], m_green->ySampling);
            }
            if (m_red && (m_red->xSampling != 1 || m_red->ySampling != 1)) {
                UpSample(data + 2 * xstep, 3, step / xstep, xsample[2], m_red->ySampling);
            }
        } else if (m_green && (m_green->xSampling != 1 || m_green->ySampling != 1)) {
            UpSample(data, 1, step / xstep, xsample[0], m_green->ySampling);
        }
    } else {
        uchar* out = data;
        int x, y;
        for (y = m_datawindow.min.y; y <= m_datawindow.max.y; y++) {
            m_file->readPixels(y, y);

            if (rgbtogray) {
                if (xsample[0] != 1) {
                    UpSampleX((float*)buffer, 3, xsample[0]);
                }
                if (xsample[1] != 1) {
                    UpSampleX((float*)buffer + 4, 3, xsample[1]);
                }
                if (xsample[2] != 1) {
                    UpSampleX((float*)buffer + 8, 3, xsample[2]);
                }

                RGBToGray((float*)buffer, (float*)out);
            } else {
                if (xsample[0] != 1) {
                    UpSampleX((float*)buffer, 3, xsample[0]);
                }
                if (xsample[1] != 1) {
                    UpSampleX((float*)(buffer + 4), 3, xsample[1]);
                }
                if (xsample[2] != 1) {
                    UpSampleX((float*)(buffer + 8), 3, xsample[2]);
                }

                if (chromatorgb) {
                    ChromaToBGR((float*)buffer, 1, step);
                }

                if (m_type == FLOAT) {
                    float* fi = (float*)buffer;
                    for (x = 0; x < m_width * 3; x++) {
                        int t = cvRound(fi[x] * 5);
                        out[x] = CV_CAST_8U(t);
                    }
                } else {
                    uint* ui = (uint*)buffer;
                    for (x = 0; x < m_width * 3; x++) {
                        uint t = ui[x];
                        out[x] = CV_CAST_8U(t);
                    }
                }
            }

            out += step;
        }
        if (color) {
            if (m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1)) {
                UpSampleY(data, 3, step / xstep, m_blue->ySampling);
            }
            if (m_green && (m_green->xSampling != 1 || m_green->ySampling != 1)) {
                UpSampleY(data + xstep, 3, step / xstep, m_green->ySampling);
            }
            if (m_red && (m_red->xSampling != 1 || m_red->ySampling != 1)) {
                UpSampleY(data + 2 * xstep, 3, step / xstep, m_red->ySampling);
            }
        } else if (m_green && (m_green->xSampling != 1 || m_green->ySampling != 1)) {
            UpSampleY(data, 1, step / xstep, m_green->ySampling);
        }
    }

    if (chromatorgb) {
        ChromaToBGR((float*)data, m_height, step / xstep);
    }

    Close();

    return result;
}

/**
// on entry pixel values are stored packed in the upper left corner of the image
// this functions expands them by duplication to cover the whole image
 */
void  ExrDecoder::UpSample(uchar* data, int xstep, int ystep, int xsample, int ysample) {
    for (int y = (m_height - 1) / ysample, yre = m_height - ysample; y >= 0; y--, yre -= ysample) {
        for (int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample) {
            for (int i = 0; i < ysample; i++) {
                for (int n = 0; n < xsample; n++) {
                    if (!m_native_depth) {
                        data[(yre + i) * ystep + (xre + n) * xstep] = data[y * ystep + x * xstep];
                    } else if (m_type == FLOAT) {
                        ((float*)data)[(yre + i) * ystep + (xre + n) * xstep] = ((float*)data)[y * ystep + x * xstep];
                    } else {
                        ((uint*)data)[(yre + i) * ystep + (xre + n) * xstep] = ((uint*)data)[y * ystep + x * xstep];
                    }
                }
            }
        }
    }
}

/**
// on entry pixel values are stored packed in the upper left corner of the image
// this functions expands them by duplication to cover the whole image
 */
void  ExrDecoder::UpSampleX(float* data, int xstep, int xsample) {
    for (int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample) {
        for (int n = 0; n < xsample; n++) {
            if (m_type == FLOAT) {
                ((float*)data)[(xre + n) * xstep] = ((float*)data)[x * xstep];
            } else {
                ((uint*)data)[(xre + n) * xstep] = ((uint*)data)[x * xstep];
            }
        }
    }
}

/**
// on entry pixel values are stored packed in the upper left corner of the image
// this functions expands them by duplication to cover the whole image
 */
void  ExrDecoder::UpSampleY(uchar* data, int xstep, int ystep, int ysample) {
    for (int y = m_height - ysample, yre = m_height - ysample; y >= 0; y -= ysample, yre -= ysample) {
        for (int x = 0; x < m_width; x++) {
            for (int i = 1; i < ysample; i++) {
                if (!m_native_depth) {
                    data[(yre + i) * ystep + x * xstep] = data[y * ystep + x * xstep];
                } else if (m_type == FLOAT) {
                    ((float*)data)[(yre + i) * ystep + x * xstep] = ((float*)data)[y * ystep + x * xstep];
                } else {
                    ((uint*)data)[(yre + i) * ystep + x * xstep] = ((uint*)data)[y * ystep + x * xstep];
                }
            }
        }
    }
}

/**
// algorithm from ImfRgbaYca.cpp
 */
void  ExrDecoder::ChromaToBGR(float* data, int numlines, int step) {
    int x, y, t;

    for (y = 0; y < numlines; y++) {
        for (x = 0; x < m_width; x++) {
            double b, Y, r;
            if (!m_native_depth) {
                b = ((uchar*)data)[y * step + x * 3];
                Y = ((uchar*)data)[y * step + x * 3 + 1];
                r = ((uchar*)data)[y * step + x * 3 + 2];
            } else if (m_type == FLOAT) {
                b = data[y * step + x * 3];
                Y = data[y * step + x * 3 + 1];
                r = data[y * step + x * 3 + 2];
            } else {
                b = ((uint*)data)[y * step + x * 3];
                Y = ((uint*)data)[y * step + x * 3 + 1];
                r = ((uint*)data)[y * step + x * 3 + 2];
            }
            r = (r + 1) * Y;
            b = (b + 1) * Y;
            Y = (Y - b * m_chroma.blue[1] - r * m_chroma.red[1]) / m_chroma.green[1];

            if (!m_native_depth) {
                int t = cvRound(b);
                ((uchar*)data)[y * step + x * 3] = CV_CAST_8U(t);
                t = cvRound(Y);
                ((uchar*)data)[y * step + x * 3 + 1] = CV_CAST_8U(t);
                t = cvRound(r);
                ((uchar*)data)[y * step + x * 3 + 2] = CV_CAST_8U(t);
            } else if (m_type == FLOAT) {
                data[y * step + x * 3] = (float)b;
                data[y * step + x * 3 + 1] = (float)Y;
                data[y * step + x * 3 + 2] = (float)r;
            } else {
                int t = cvRound(b);
                ((uint*)data)[y * step + x * 3] = (uint)MAX(t, 0);
                t = cvRound(Y);
                ((uint*)data)[y * step + x * 3 + 1] = (uint)MAX(t, 0);
                t = cvRound(r);
                ((uint*)data)[y * step + x * 3 + 2] = (uint)MAX(t, 0);
            }
        }
    }
}


/**
// convert one row to gray
*/
void  ExrDecoder::RGBToGray(float* in, float* out) {
    if (m_type == FLOAT) {
        if (m_native_depth) {
            for (int i = 0, n = 0; i < m_width; i++, n += 3) {
                out[i] = in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0];
            }
        } else {
            uchar* o = (uchar*)out;
            for (int i = 0, n = 0; i < m_width; i++, n += 3) {
                o[i] = (uchar)(in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0]);
            }
        }
    } else { // UINT
        if (m_native_depth) {
            uint* ui = (uint*)in;
            for (int i = 0; i < m_width * 3; i++) {
                ui[i] -= 0x80000000;
            }
            int* si = (int*)in;
            for (int i = 0, n = 0; i < m_width; i++, n += 3) {
                ((int*)out)[i] = int(si[n] * m_chroma.blue[0] + si[n + 1] * m_chroma.green[0] + si[n + 2] * m_chroma.red[0]);
            }
        } else { // how to best convert float to uchar?
            uint* ui = (uint*)in;
            for (int i = 0, n = 0; i < m_width; i++, n += 3) {
                ((uchar*)out)[i] = uchar((ui[n] * m_chroma.blue[0] + ui[n + 1] * m_chroma.green[0] + ui[n + 2] * m_chroma.red[0]) * (256.0 / 4294967296.0));
            }
        }
    }
}

/////////////////////// ExrEncoder ///////////////////


ExrEncoder::ExrEncoder() {
    m_description = "OpenEXR Image files (*.exr)";
}


ExrEncoder::~ExrEncoder() {
}


bool  ExrEncoder::isFormatSupported(int depth) const {
    return CV_MAT_DEPTH(depth) >= CV_8U && CV_MAT_DEPTH(depth) < CV_64F;
}


// TODO scale appropriately
bool  ExrEncoder::write(const Mat& img, const Vector<int>&) {
    int width = img.cols, height = img.rows;
    int depth = img.depth(), channels = img.channels();
    bool result = false;
    bool issigned = depth == CV_8S || depth == CV_16S || depth == CV_32S;
    bool isfloat = depth == CV_32F || depth == CV_64F;
    depth = CV_ELEM_SIZE1(depth) * 8;
    uchar* data = img.data;
    int step = img.step;

    Header header(width, height);
    PixelType type;

    if (depth == 8) {
        type = HALF;
    } else if (isfloat) {
        type = FLOAT;
    } else {
        type = UINT;
    }

    if (channels == 3) {
        header.channels().insert("R", Channel(type));
        header.channels().insert("G", Channel(type));
        header.channels().insert("B", Channel(type));
        //printf("bunt\n");
    } else {
        header.channels().insert("Y", Channel(type));
        //printf("gray\n");
    }

    OutputFile file(m_filename.c_str(), header);

    FrameBuffer frame;

    char* buffer;
    int bufferstep;
    int size;
    if (type == FLOAT && depth == 32) {
        buffer = (char*)const_cast<uchar*>(data);
        bufferstep = step;
        size = 4;
    } else if (depth > 16 || type == UINT) {
        buffer = (char*)new uint[width * channels];
        bufferstep = 0;
        size = 4;
    } else {
        buffer = (char*)new half[width * channels];
        bufferstep = 0;
        size = 2;
    }

    //printf("depth %d %s\n", depth, types[type]);

    if (channels == 3) {
        frame.insert("B", Slice(type, buffer, size * 3, bufferstep));
        frame.insert("G", Slice(type, buffer + size, size * 3, bufferstep));
        frame.insert("R", Slice(type, buffer + size * 2, size * 3, bufferstep));
    } else {
        frame.insert("Y", Slice(type, buffer, size, bufferstep));
    }

    file.setFrameBuffer(frame);

    int offset = issigned ? 1 << (depth - 1) : 0;

    result = true;
    if (type == FLOAT && depth == 32) {
        try {
            file.writePixels(height);
        } catch (...) {
            result = false;
        }
    } else {
        //    int scale = 1 << (32 - depth);
        //    printf("scale %d\n", scale);
        for (int line = 0; line < height; line++) {
            if (type == UINT) {
                uint* buf = (uint*)buffer;  // FIXME 64-bit problems

                if (depth <= 8) {
                    for (int i = 0; i < width * channels; i++) {
                        buf[i] = data[i] + offset;
                    }
                } else if (depth <= 16) {
                    unsigned short* sd = (unsigned short*)data;
                    for (int i = 0; i < width * channels; i++) {
                        buf[i] = sd[i] + offset;
                    }
                } else {
                    int* sd = (int*)data;  // FIXME 64-bit problems
                    for (int i = 0; i < width * channels; i++) {
                        buf[i] = (uint) sd[i] + offset;
                    }
                }
            } else {
                half* buf = (half*)buffer;

                if (depth <= 8) {
                    for (int i = 0; i < width * channels; i++) {
                        buf[i] = data[i];
                    }
                } else if (depth <= 16) {
                    unsigned short* sd = (unsigned short*)data;
                    for (int i = 0; i < width * channels; i++) {
                        buf[i] = sd[i];
                    }
                }
            }
            try {
                file.writePixels(1);
            } catch (...) {
                result = false;
                break;
            }
            data += step;
        }
        delete buffer;
    }

    return result;
}

}

#endif

/* End of file. */
